<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> 
    <title> - 天地维杰网</title>
    <meta name="keywords" content="系统架构,shutdown,不与天斗,Domino,博客,程序员,架构师,笔记,技术,分享,java,Redis">
    
    <meta property="og:title" content="">
    <meta property="og:site_name" content="天地维杰网">
    <meta property="og:image" content="/img/author.jpg"> 
    <meta name="title" content=" - 天地维杰网" />
    <meta name="description" content="天地维杰网 | 博客 | 软件 | 架构 | Java "> 
    <link rel="shortcut icon" href="http://www.shutdown.cn/img/favicon.ico" />
    <link rel="apple-touch-icon" href="http://www.shutdown.cn/img/apple-touch-icon.png" />
    <link rel="apple-touch-icon-precomposed" href="http://www.shutdown.cn/img/apple-touch-icon.png" />
    <link href="http://www.shutdown.cn/js/vendor/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />
    <link href="http://www.shutdown.cn/js/vendor/fancybox/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />
    <link href="http://www.shutdown.cn/css/main.css" rel="stylesheet" type="text/css" />
    <link href="http://www.shutdown.cn/css/syntax.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" id="hexo.configuration">
  var NexT = window.NexT || {};
  var CONFIG = {
    scheme: 'Pisces',
    sidebar: {"position":"left","display":"post"},
     fancybox: true, 
    motion: true
  };
</script>
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-7826003325059020" crossorigin="anonymous"></script>
</head>
<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">
<div class="container one-collumn sidebar-position-left page-home  ">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"> <div class="site-meta  custom-logo ">

  <div class="custom-logo-site-title">
    <a href="http://www.shutdown.cn"  class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <span class="site-title">天地维杰网</span>
      <span class="logo-line-after"><i></i></span>
    </a>
  </div>
  <p class="site-subtitle">人如秋鸿来有信，事若春梦了无痕</p>
</div>

<div class="site-nav-toggle">
  <button>
    <span class="btn-bar"></span>
    <span class="btn-bar"></span>
    <span class="btn-bar"></span>
  </button>
</div>

<nav class="site-nav">
    <ul id="menu" class="menu">
      
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />首页
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/redis/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-battery-full"></i> <br />Redis
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/java/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-coffee"></i> <br />java
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/linux/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-linux"></i> <br />linux
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/daily/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-bug"></i> <br />日常问题
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/spring/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-child"></i> <br />Spring和Springboot
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/mac/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-fire"></i> <br />Mac相关
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/middleware/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-gavel"></i> <br />中间件
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/jiagou/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-rocket"></i> <br />架构
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/python/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-ship"></i> <br />python
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/front/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-bolt"></i> <br />前端
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/jvm/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-balance-scale"></i> <br />jvm
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/c/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-battery-empty"></i> <br />c语言
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/web3/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-web3"></i> <br />web3
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/post/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br />归档
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/about/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-user"></i> <br />关于
          </a>
        </li>
      
      <li class="menu-item menu-item-search">
        <a href="javascript:;" class="popup-trigger"> <i class="menu-item-icon fa fa-search fa-fw"></i> <br /> 搜索</a>
      </li>
    </ul>
    <div class="site-search">
      <div class="popup">
 <span class="search-icon fa fa-search"></span>
 <input type="text" id="local-search-input">
 <div id="local-search-result"></div>
 <span class="popup-btn-close">close</span>
</div>

    </div>
</nav>

 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            
<section id="posts" class="posts-expand">
  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <header class="post-header">
      <h1 class="post-title" itemprop="name headline">
        <a class="post-title-link" href="http://www.shutdown.cn/post/redis6.0%E6%96%B0%E7%89%B9%E6%80%A7-%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%BC%93%E5%AD%98%E5%85%B6%E5%85%AD-%E5%90%84%E6%A8%A1%E5%BC%8F%E5%91%BD%E4%BB%A4%E6%B5%8B%E8%AF%95/" itemprop="url">
        
        </a>
      </h1>
      <div class="post-meta">
      <span class="post-time">
<span class="post-meta-item-icon">
    <i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">时间：</span>
<time itemprop="dateCreated" datetime="2016-03-22T13:04:35+08:00" content="0001-01-01">
    0001-01-01
</time>
</span> 
      
      
       <span>
&nbsp; | &nbsp;
<span class="post-meta-item-icon">
    <i class="fa fa-eye"></i>
</span>
<span class="post-meta-item-text">阅读：</span>
<span class="leancloud-visitors-count">806 字 ~4分钟</span>
</span>
      </div>
    </header>
    <div class="post-body" itemprop="articleBody">
    

    

<blockquote>
<p><a href="https://www.cnblogs.com/remcarpediem/p/12872053.html">https://www.cnblogs.com/remcarpediem/p/12872053.html</a></p>
</blockquote>

<h1 id="带你100-地了解-redis-6-0-的客户端缓存">带你100% 地了解 Redis 6.0 的客户端缓存</h1>

<p>近日 Redis 6.0.0 GA 版本发布，这是 Redis 历史上最大的一次版本更新，包括了客户端缓存 (Client side caching)、ACL、Threaded I/O 和 Redis Cluster Proxy 等诸多更新。</p>

<p>我们今天就依次聊一下客户端缓存的必要性、具体使用、原理分析和实现。</p>

<h3 id="为什么需要客户端缓存">为什么需要客户端缓存？</h3>

<p>我们都知道，使用 Redis 进行数据的缓存的主要目的是减少对 MySQL 等数据库的访问，提供更快的访问速度，毕竟 《Redis in Action》中提到的， Redis 的性能大致是普通关系型数据库的 10 ~ 100 倍。</p>

<p>所以，如下图所示，Redis 用来存储热点数据，Redis 未命中，再去访问数据库，这样可以应付大多数情况下的性能要求。</p>

<div  align="center"><img src="https://wejack639.oss-cn-beijing.aliyuncs.com/blogimages/img/20220630145633.png" width = 300 /> </div>

<p>但是，Redis 也有其性能上限，并且访问 Redis 必然有一定的网络 I/O 以及序列化反序列化损耗。所以，往往会引入进程缓存，将最热的数据存储在本地，进一步加快访问速度。</p>

<div  align="center"><img src="https://wejack639.oss-cn-beijing.aliyuncs.com/blogimages/img/20220630145711.png" width = 400 /> </div>

<p>如上图所示，Guava Cache 等进程缓存作为一级缓存，Redis 作为二级缓存：</p>

<ol>
<li>先去 Guava Cache 中查询数据，如果命中则直接返回。</li>
<li>Guava Cache 中未命中，则再去 Redis 中查询，如果命中则返回数据，并在 Guava Cache 中设置此数据。</li>
<li>Redis 也未命中的话，只有去 MySQL 中查询，然后依次将数据设置到 Redis 和 Guava Cache 中。</li>
</ol>

<p>只使用 Redis 分布式缓存时，遇到数据更新时，应用程序更新完 MySQL 中的数据，可以直接将 Redis 中对应缓存失效掉，保持数据的一致性。</p>

<p>而进程内缓存的数据一致性比分布式的缓存面临更大的挑战。数据更新的时候，如何通知其他进程也更新自己的缓存呢?</p>

<p>如果按照分布式缓存的思路，我们可以设置极短的缓存失效时间，这样不必实现复杂的通知机制。</p>

<p>但是不同进程内的数据依然会面临不一致的问题，并且不同进程缓存失效时间不统一，同一个请求到了不同的进程，可能出现反复幻读的情况。</p>

<div  align="center"><img src="https://wejack639.oss-cn-beijing.aliyuncs.com/blogimages/img/20220630145738.png" width = 800 /> </div>

<p>Ben 在 RedisConf18 给出了一个方案(视频和 PPT 链接在文末)，通过 Redis 的 Pub/Sub，可以通知其他进程缓存对此缓存进行删除。如果 Redis 挂了或者订阅机制不靠谱，依靠超时设定，依然可以做兜底处理。</p>

<p>Antirez(Redis 的作者) 也正是听取 Ben 这个方案后，才决定在 Redis Server 支持客户端缓存的，因为在有服务端参与的情况下可以更好的处理上述这些问题。</p>

<h3 id="功能介绍和演示">功能介绍和演示</h3>

<p>下面使用 Docker 安装 Redis 6.0.1，然后使用 telnet 来简单演示一下 Redis 6.0 的客户端缓存功能。所有相关的功能如下图所示，分别是使用RESP3 协议版本的普通模式和广播模式，以及使用 RESP2 协议版本的转发模式。我们先来看普通模式。</p>

<div  align="center"><img src="https://wejack639.oss-cn-beijing.aliyuncs.com/blogimages/img/20220630145755.png" width = 600 /> </div>

<h4 id="普通模式">普通模式</h4>

<p>先使用 redis-cli 设置缓存值 test=111，使用 telnet 连接上 Redis，然后发送 hello 3 开启 RESP3 协议。</p>
<div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#ce5c00;font-weight:bold">[</span>root@VM_0_3_centos ~<span style="color:#ce5c00;font-weight:bold">]</span><span style="color:#8f5902;font-style:italic"># telnet 127.0.0.1 6379</span>
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is <span style="color:#4e9a06">&#39;^]&#39;</span>.
hello <span style="color:#0000cf;font-weight:bold">3</span>
// telnet 输出结果格式化标准化后如下，否则换行太多并且是 RESP3 格式，不需要了解格式。
&gt; HELLO <span style="color:#0000cf;font-weight:bold">3</span>
1# <span style="color:#4e9a06">&#34;server&#34;</span> <span style="color:#ce5c00;font-weight:bold">=</span>&gt; <span style="color:#4e9a06">&#34;redis&#34;</span>
2# <span style="color:#4e9a06">&#34;version&#34;</span> <span style="color:#ce5c00;font-weight:bold">=</span>&gt; <span style="color:#4e9a06">&#34;6.0.1&#34;</span>
3# <span style="color:#4e9a06">&#34;proto&#34;</span> <span style="color:#ce5c00;font-weight:bold">=</span>&gt; <span style="color:#ce5c00;font-weight:bold">(</span>integer<span style="color:#ce5c00;font-weight:bold">)</span> <span style="color:#0000cf;font-weight:bold">3</span>
4# <span style="color:#4e9a06">&#34;id&#34;</span> <span style="color:#ce5c00;font-weight:bold">=</span>&gt; <span style="color:#ce5c00;font-weight:bold">(</span>integer<span style="color:#ce5c00;font-weight:bold">)</span> <span style="color:#0000cf;font-weight:bold">10</span>
5# <span style="color:#4e9a06">&#34;mode&#34;</span> <span style="color:#ce5c00;font-weight:bold">=</span>&gt; <span style="color:#4e9a06">&#34;standalone&#34;</span>
6# <span style="color:#4e9a06">&#34;role&#34;</span> <span style="color:#ce5c00;font-weight:bold">=</span>&gt; <span style="color:#4e9a06">&#34;master&#34;</span>
7# <span style="color:#4e9a06">&#34;modules&#34;</span> <span style="color:#ce5c00;font-weight:bold">=</span>&gt; <span style="color:#ce5c00;font-weight:bold">(</span>empty array<span style="color:#ce5c00;font-weight:bold">)</span></code></pre></div>
<p>这里需要注意，<strong>Redis 服务端只会 track 客户端在一个连接生命周期内的获取的只读命令的 key值</strong>。<strong>Redis 客户端默认不开启 track 模式</strong>，需要使用命令开启，然后必须要先获取一次 test 的值，这样 Redis 服务器才会记录它。</p>
<div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">client tracking on
+OK
get <span style="color:#204a87">test</span>
<span style="color:#000">$3</span>
<span style="color:#0000cf;font-weight:bold">111</span></code></pre></div>
<p><strong>当键被修改，或者因为失效时间(expire time)和内存上限 maxmemory 策略被驱除时，Redis 服务端会通知这些客户端</strong>。我们这里简单地更新 test 的值，telnet 则会收到如下通知</p>
<div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">&gt;2 // RESP3 中的 PUSH 类型，标志为 &gt; 符号
<span style="color:#000">$10</span>
invalidate
*1
<span style="color:#000">$4</span>
test</code></pre></div>
<p>如果你再一次更新 test 值，这次 telnet 就不会再收到失效(invalidate)消息。除非 telnet 再进行一次 get 操作，重新 tracking 对应的键值。</p>

<p><strong>也就是说 Redis 服务端记录的客户端 track 信息只生效一次，发送过失效消息后就会删除，只有下次客户端再次执行只读命令被 track，才会进行下一次消息通知</strong> 。</p>

<p>取消 tracking 的命令如下所示。</p>
<div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">client tracking off
<span style="color:#00a000">+OK
</span></code></pre></div>
<h4 id="广播模式">广播模式</h4>

<p>Redis 还提供了一种广播模式(BCAST)，它是另外一种客户端缓存的实现方式。这种方式下 <strong>Redis 服务端不再消耗过多内存存储信息，而是发送更多的失效消息给客户端</strong>。</p>

<p>这是服务端存储过多数据，消耗内存和客户端收到过多消息，消耗网络带宽之间的权衡(tradeoff)。</p>
<div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">// 已经 hello <span style="color:#0000cf;font-weight:bold">3</span> 开启 RESP3 协议，不然无法收到失效消息，下同
client tracking on bcast 
+OK
// 此时设置 key 为 a 的键值，收到如下消息。
&gt;2
<span style="color:#000">$10</span>
invalidate
*1
<span style="color:#000">$1</span>
a</code></pre></div>
<p>如果你不想所有的键值的失效消息都收到，则可以限制 key 的前缀，如下命令则表示只关注前缀为 test 的键值的消息。一般来说，业务的缓存 key 都是根据业务拥有统一的前缀，所以这一特性十分方便。</p>
<div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-fallback" data-lang="fallback">client tracking on bcast prefix test</code></pre></div>
<p>与普通模式必须获取一次键的规则不同，<strong>广播模式下，只要键被修改或删除，符合规则的客户端都会收到失效消息，而且是可以多次获取的</strong></p>

<p>与普通模式相比，虽然少存储了一些数据，但是由于需要对前缀规则进行匹配，会消耗一定的 CPU 资源，所以<strong>注意别使用过长的前缀</strong>。</p>

<h4 id="转发模式">转发模式</h4>

<p>上述操作时客户端都需要先开启 RESP3，Redis 为了兼容 RESP2 协议提供了转发(Redirect)模式，不再使用 RESP3 原生支持 PUSH 消息，而是将消息通过 Pub/Sub 通知给另外一个客户端，具体流程如下图所示。</p>

<div  align="center"><img src="https://wejack639.oss-cn-beijing.aliyuncs.com/blogimages/img/20220630145847.png" width = 600 /> </div>

<p>这里需要两个 telnet，其中一个 telnet 需要订阅 <code>_redis_:invalidate</code> 信道。然后另一个 telnet 开启 Redirect 模式，并制定将失效消息通过订阅信道发送给第一个 telnet。</p>
<div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#8f5902;font-style:italic"># telent B</span>
client id
:368
subscribe _redis_:invalidate

<span style="color:#8f5902;font-style:italic"># telnet A，开启 track 并指定转发给 B</span>
client tracking on bcast redirect <span style="color:#0000cf;font-weight:bold">368</span>

<span style="color:#8f5902;font-style:italic"># telent B 此时有键值被修改，收到 __redis__:invalidate 信道的消息</span>
message
<span style="color:#000">$20</span>
__redis__:invalidate
*1
<span style="color:#000">$1</span>
a</code></pre></div>
<p>你会发现，转发模式和文章开始提到的多级缓存中的更新机制很类似了，只不过那个方案中是业务系统修改完 key 后发送消息通知，而这里是 Redis 服务端代替业务系统发送消息通知。</p>

<h4 id="optin-和-optout-选项">OPTIN 和 OPTOUT 选项</h4>

<p>使用 OPTIN 可以选择性的开启 tracking。只有你发送 client caching yes (Redis 文档中是 CACHING 命令，但是实验时发现无效)之后的下一条的只读命令的 key 才会 tracking，否则其他的只读命令的 key 不会被 tracking。</p>
<div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift"><span style="color:#000">client</span> <span style="color:#000">tracking</span> <span style="color:#000">on</span> <span style="color:#000">optin</span>
<span style="color:#000">client</span> <span style="color:#000">caching</span> <span style="color:#000">yes</span>
<span style="color:#204a87;font-weight:bold">get</span> <span style="color:#000">a</span>
<span style="color:#204a87;font-weight:bold">get</span> <span style="color:#000">b</span>
<span style="color:#8f5902;font-style:italic">//</span><span style="color:#8f5902;font-style:italic"> </span><span style="color:#8f5902;font-style:italic">此</span><span style="color:#8f5902;font-style:italic">时</span><span style="color:#8f5902;font-style:italic">修</span><span style="color:#8f5902;font-style:italic">改</span><span style="color:#8f5902;font-style:italic"> </span><span style="color:#8f5902;font-style:italic">a</span><span style="color:#8f5902;font-style:italic"> </span><span style="color:#8f5902;font-style:italic">和</span><span style="color:#8f5902;font-style:italic"> </span><span style="color:#8f5902;font-style:italic">b</span><span style="color:#8f5902;font-style:italic"> </span><span style="color:#8f5902;font-style:italic">的</span><span style="color:#8f5902;font-style:italic">值</span><span style="color:#8f5902;font-style:italic">，</span><span style="color:#8f5902;font-style:italic">发</span><span style="color:#8f5902;font-style:italic">现</span><span style="color:#8f5902;font-style:italic">只</span><span style="color:#8f5902;font-style:italic">收</span><span style="color:#8f5902;font-style:italic">到</span><span style="color:#8f5902;font-style:italic"> </span><span style="color:#8f5902;font-style:italic">a</span><span style="color:#8f5902;font-style:italic"> </span><span style="color:#8f5902;font-style:italic">的</span><span style="color:#8f5902;font-style:italic">失</span><span style="color:#8f5902;font-style:italic">效</span><span style="color:#8f5902;font-style:italic">消</span><span style="color:#8f5902;font-style:italic">息</span>
<span style="color:#ce5c00;font-weight:bold">&gt;</span><span style="color:#0000cf;font-weight:bold">2</span>
<span style="color:#000">$10</span>
<span style="color:#000">invalidate</span>
<span style="color:#ce5c00;font-weight:bold">*</span><span style="color:#0000cf;font-weight:bold">1</span>
<span style="color:#000">$1</span>
<span style="color:#000">a</span></code></pre></div>
<p>而 OPTOUT 参数与之相反，你可以有选择的退出 tracking。发送 client caching off 之后的下一条只读命令的 key 不会被 tracking，其他只读命令都会被 tracking。</p>

<p>OPTIN 和 OPTOUT 是针对非 BCAST 模式，也就是只有发送了某个 key 的只读命令后，才会追踪相应的 key。而 BCAST 模式是无论你是否发送某个 key 的只读命令，只有 Redis 修改了 key，都会发送相应的 key 的失效消息(前缀匹配的)。</p>

<h4 id="noloop-选项">NOLOOP 选项</h4>

<p>默认情况下，失效消息会发送给所有需要的 Redis 客户端，但是有些情况下触发失效消息也就是更新 key 的客户端不需要收到该消息。</p>

<p>设置 NOLOOP，可以避免这种情况，更新 Key 的客户端将不再收到消息，该选项在普通模式和广播模式下都适用。</p>

<h4 id="最大-tracking-上限-tracking-table-max-keys">最大 tracking 上限 tracking_table_max_keys</h4>

<p>由上文可以知道，普通模式下需要存储大量的被 tracking 的 key 和客户端信息(具体存储的数据下文中会讲解)，所以当 10k 客户端使用该模式处理百万个键时，会消耗大量的内存空间，所以 Redis 引入了 tracking_table_max_keys 配置，默认为无，不限制。</p>

<p>当有一个新的键被 tracking 时，如果当前 tracking 的 key 的数量大于 tracking_table_max_keys，则会随机删除之前 tracking 的 key，并且向对应的客户端发送失效消息。</p>

<h3 id="原理和源码实现">原理和源码实现</h3>

<h4 id="普通模式原理">普通模式原理</h4>

<p>我们也先讲解普通模式的原理，Redis 服务端使用 TrackingTable 存储普通模式的客户端数据，它的数据类型是基数树(radix tree)。</p>

<p>基数树是针对稀疏的长整型数据查找的多叉搜索树，能快速且节省空间的完映射，一般用于解决 Hash冲突和 Hash表大小的设计问题，Linux 的内存管理就使用了它。</p>

<div  align="center"><img src="https://wejack639.oss-cn-beijing.aliyuncs.com/blogimages/img/20220630145914.png" width = 400 /> </div>

<p>Redis 用它存储<strong>键的指针</strong>和<strong>客户端 ID</strong> 的映射关系。因为键对象的指针就是内存地址，也就是长整型数据。客户端缓存的相关操作就是对该数据的增删改查：</p>

<ul>
<li>当开启 track 功能的客户端获取某一个键值时，Redis 会调用 <code>enableTracking</code> 方法使用基数树记录下该 key 和 clientId 的映射关系。</li>
<li>当某一个 key 被修改或删除时，Redis 会调用 <code>trackingInvalidateKey</code> 方法根据 key 从 TrackingTable 中查找所有对应的客户端ID，然后调用 <code>sendTrackingMessage</code> 方法发送失效消息给这些客户端(会检查 CLIENT_TRACKING 相关标志位是否开启和是否开启了 NOLOOP)。</li>
<li>发送完失效消息后，根据<strong>键的指针值</strong>将映射关系从 TrackingTable中删除。</li>
<li>客户端关闭 track 功能后，因为删除需要进行大量操作，所以 Redis 使用懒删除方式，只是将该客户端的 CLIENT_TRACKING 相关标志位删除掉。</li>
</ul>

<h4 id="广播模式原理">广播模式原理</h4>

<div  align="center"><img src="https://wejack639.oss-cn-beijing.aliyuncs.com/blogimages/img/20220630150804.png" width = 400 /> </div>

<p>广播模式与普通模式类似，Redis 同样使用 <code>PrefixTable</code> 存储广播模式下的客户端数据，它存储<strong>前缀字符串指针和(需要通知的key和客户端ID)</strong>的映射关系。它和广播模式最大的区别就是真正发送失效消息的时机不同：</p>

<ul>
<li>当客户端开启广播模式时，会在 <code>PrefixTable</code>的前缀对应的客户端列表中加入该客户端ID。</li>
<li>当某一个 key 被修改或删除时，Redis 会调用 <code>trackingInvalidateKey</code> 方法，<code>trackingInvalidateKey</code> 方法中如果发现 <code>PrefixTable</code> 不为空，则调用 <code>trackingRememberKeyToBroadcast</code> 依次遍历所有前缀，如果key 符合前缀规则，则记录到 <code>PrefixTable</code> 对应的位置。</li>
<li>在 Redis 的事件处理周期函数 beforeSleep 函数里会调用 <code>trackingBroadcastInvalidationMessages</code> 函数来真正发送消息。</li>
</ul>

<h4 id="处理最大-tracking-上限">处理最大 tracking 上限</h4>

<p>Redis 会在每次执行过命令后(processCommand方法)调用 <code>trackingLimitUsedSlots</code> 来判断是否需要进行清理：</p>

<ul>
<li>判断 TrackingTable 中键的数量是否大于 tracking_table_max_keys；</li>
<li>在一定时间段内(不能太长，阻塞主流程)，随机从 TrackingTable 中选出一个键删除，直到数量小于或者时间用完为止。</li>
</ul>

<h4 id="具体源码">具体源码</h4>

<p>关于源码，在 tracking.c 文件下，我们这里只看一下最为关键的 <code>trackingInvalidateKey</code> 函数和 <code>sendTrackingMessage</code> 函数，理解了这两个函数，广播模式和处理最大 tracking 上限等相关函数都与之类似。</p>
<div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c" data-lang="c"><span style="color:#204a87;font-weight:bold">void</span> <span style="color:#000">trackingInvalidateKey</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">client</span> <span style="color:#ce5c00;font-weight:bold">*</span><span style="color:#000">c</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">robj</span> <span style="color:#ce5c00;font-weight:bold">*</span><span style="color:#000">keyobj</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#000;font-weight:bold">{</span>
    <span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000;font-weight:bold">(</span><span style="color:#000">TrackingTable</span> <span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87">NULL</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#204a87;font-weight:bold">return</span><span style="color:#000;font-weight:bold">;</span>
    <span style="color:#000">sds</span> <span style="color:#000">sdskey</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">keyobj</span><span style="color:#ce5c00;font-weight:bold">-</span><span style="color:#ce5c00;font-weight:bold">&gt;</span><span style="color:#000">ptr</span><span style="color:#000;font-weight:bold">;</span>
    <span style="color:#8f5902;font-style:italic">// 省略，如果广播模式的记录基数树不为空，则先处理广播模式
</span><span style="color:#8f5902;font-style:italic"></span>    <span style="color:#8f5902;font-style:italic">// 1 根据键的指针去 TrackingTable 查找
</span><span style="color:#8f5902;font-style:italic"></span>    <span style="color:#000">rax</span> <span style="color:#ce5c00;font-weight:bold">*</span><span style="color:#000">ids</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">raxFind</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">TrackingTable</span><span style="color:#000;font-weight:bold">,</span><span style="color:#000;font-weight:bold">(</span><span style="color:#204a87;font-weight:bold">unsigned</span> <span style="color:#204a87;font-weight:bold">char</span><span style="color:#ce5c00;font-weight:bold">*</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000">sdskey</span><span style="color:#000;font-weight:bold">,</span><span style="color:#000">sdslen</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">sdskey</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">;</span>
    <span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000;font-weight:bold">(</span><span style="color:#000">ids</span> <span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">raxNotFound</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#204a87;font-weight:bold">return</span><span style="color:#000;font-weight:bold">;</span>
    <span style="color:#8f5902;font-style:italic">// 2 使用迭代器遍历
</span><span style="color:#8f5902;font-style:italic"></span>    <span style="color:#000">raxIterator</span> <span style="color:#000">ri</span><span style="color:#000;font-weight:bold">;</span><span style="color:#000">raxStart</span><span style="color:#000;font-weight:bold">(</span><span style="color:#ce5c00;font-weight:bold">&amp;</span><span style="color:#000">ri</span><span style="color:#000;font-weight:bold">,</span><span style="color:#000">ids</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">;</span><span style="color:#000">raxSeek</span><span style="color:#000;font-weight:bold">(</span><span style="color:#ce5c00;font-weight:bold">&amp;</span><span style="color:#000">ri</span><span style="color:#000;font-weight:bold">,</span><span style="color:#4e9a06"></span><span style="color:#4e9a06">&#34;</span><span style="color:#4e9a06">^</span><span style="color:#4e9a06">&#34;</span><span style="color:#000;font-weight:bold">,</span><span style="color:#204a87">NULL</span><span style="color:#000;font-weight:bold">,</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">;</span>
    <span style="color:#204a87;font-weight:bold">while</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">raxNext</span><span style="color:#000;font-weight:bold">(</span><span style="color:#ce5c00;font-weight:bold">&amp;</span><span style="color:#000">ri</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#000;font-weight:bold">{</span>
        <span style="color:#8f5902;font-style:italic">// 3 根据 clientId 查找 client 实例
</span><span style="color:#8f5902;font-style:italic"></span>        <span style="color:#000">client</span> <span style="color:#ce5c00;font-weight:bold">*</span><span style="color:#000">target</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">lookupClientByID</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">id</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">;</span>
        <span style="color:#8f5902;font-style:italic">// 4 如果未开启 track 或者是广播模式则跳过。
</span><span style="color:#8f5902;font-style:italic"></span>        <span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000;font-weight:bold">(</span><span style="color:#000">target</span> <span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87">NULL</span> <span style="color:#ce5c00;font-weight:bold">|</span><span style="color:#ce5c00;font-weight:bold">|</span>
            <span style="color:#ce5c00;font-weight:bold">!</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">target</span><span style="color:#ce5c00;font-weight:bold">-</span><span style="color:#ce5c00;font-weight:bold">&gt;</span><span style="color:#000">flags</span> <span style="color:#ce5c00;font-weight:bold">&amp;</span> <span style="color:#000">CLIENT_TRACKING</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">|</span><span style="color:#ce5c00;font-weight:bold">|</span>
            <span style="color:#000">target</span><span style="color:#ce5c00;font-weight:bold">-</span><span style="color:#ce5c00;font-weight:bold">&gt;</span><span style="color:#000">flags</span> <span style="color:#ce5c00;font-weight:bold">&amp;</span> <span style="color:#000">CLIENT_TRACKING_BCAST</span><span style="color:#000;font-weight:bold">)</span>
        <span style="color:#000;font-weight:bold">{</span>   <span style="color:#204a87;font-weight:bold">continue</span><span style="color:#000;font-weight:bold">;</span>  <span style="color:#000;font-weight:bold">}</span>
        <span style="color:#8f5902;font-style:italic">// 5 如果开启了 NOLOOP 并且是导致key发生变化的client则跳过。
</span><span style="color:#8f5902;font-style:italic"></span>        <span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000;font-weight:bold">(</span><span style="color:#000">target</span><span style="color:#ce5c00;font-weight:bold">-</span><span style="color:#ce5c00;font-weight:bold">&gt;</span><span style="color:#000">flags</span> <span style="color:#ce5c00;font-weight:bold">&amp;</span> <span style="color:#000">CLIENT_TRACKING_NOLOOP</span> <span style="color:#ce5c00;font-weight:bold">&amp;</span><span style="color:#ce5c00;font-weight:bold">&amp;</span>
            <span style="color:#000">target</span> <span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">c</span><span style="color:#000;font-weight:bold">)</span>
        <span style="color:#000;font-weight:bold">{</span>   <span style="color:#204a87;font-weight:bold">continue</span><span style="color:#000;font-weight:bold">;</span>  <span style="color:#000;font-weight:bold">}</span>
        <span style="color:#8f5902;font-style:italic">// 6 发送失效消息
</span><span style="color:#8f5902;font-style:italic"></span>        <span style="color:#000">sendTrackingMessage</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">target</span><span style="color:#000;font-weight:bold">,</span><span style="color:#000">sdskey</span><span style="color:#000;font-weight:bold">,</span><span style="color:#000">sdslen</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">sdskey</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">,</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">;</span>
    <span style="color:#000;font-weight:bold">}</span>
    <span style="color:#8f5902;font-style:italic">// 7 减少数据统计，根据sdskey删除对应的记录
</span><span style="color:#8f5902;font-style:italic"></span>    <span style="color:#000">TrackingTableTotalItems</span> <span style="color:#ce5c00;font-weight:bold">-</span><span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">raxSize</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">ids</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">;</span>
    <span style="color:#000">raxFree</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">ids</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">;</span>
    <span style="color:#000">raxRemove</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">TrackingTable</span><span style="color:#000;font-weight:bold">,</span><span style="color:#000;font-weight:bold">(</span><span style="color:#204a87;font-weight:bold">unsigned</span> <span style="color:#204a87;font-weight:bold">char</span><span style="color:#ce5c00;font-weight:bold">*</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000">sdskey</span><span style="color:#000;font-weight:bold">,</span><span style="color:#000">sdslen</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">sdskey</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">,</span><span style="color:#204a87">NULL</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">;</span>
<span style="color:#000;font-weight:bold">}</span></code></pre></div>
<p>源码如上所示，trackingInvalidateKey 方法主要做了 7 件事情：</p>

<ul>
<li>根据键的指针去 TrackingTable 查找客户端ID列表；</li>
<li>使用迭代器遍历列表；</li>
<li>根据 clientId 查找 client 实例；</li>
<li>如果 client 实例未开启 track 或者是广播模式则跳过；</li>
<li>如果 client 实例开启了 NOLOOP 并且是导致key发生变化的client则跳过；</li>
<li>调用 sendTrackingMessage 方法发送失效消息；</li>
<li>减少数据统计，根据sdskey删除对应的记录</li>
</ul>

<p>下面来看真正发送消息的 <code>sendTrackingMessage</code> 函数，它主要做了6件事：</p>

<ul>
<li>如果 client_tracking_redirection 不为空，则开启了转发模式；</li>
<li>找到转发的客户端实例；</li>
<li>如果转发客户端关闭了，则必须通知原客户端；</li>
<li>如果是客户端使用 RESP3 则发 PUSH 消息；</li>
<li>如果是转发模式，往 TrackingChannelName 也就是 <code>_redis_:invalidate</code> 信道中发送失效消息的头部信息；</li>

<li><p>发送键等信息。</p>
<div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c" data-lang="c"><span style="color:#204a87;font-weight:bold">void</span> <span style="color:#000">sendTrackingMessage</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">client</span> <span style="color:#ce5c00;font-weight:bold">*</span><span style="color:#000">c</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#204a87;font-weight:bold">char</span> <span style="color:#ce5c00;font-weight:bold">*</span><span style="color:#000">keyname</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">size_t</span> <span style="color:#000">keylen</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#204a87;font-weight:bold">int</span> <span style="color:#000">proto</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#000;font-weight:bold">{</span>
<span style="color:#204a87;font-weight:bold">int</span> <span style="color:#000">using_redirection</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">;</span>
<span style="color:#8f5902;font-style:italic">// 1 如果 client_tracking_redirection 不为空，则开启了转发模式
</span><span style="color:#8f5902;font-style:italic"></span><span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000;font-weight:bold">(</span><span style="color:#000">c</span><span style="color:#ce5c00;font-weight:bold">-</span><span style="color:#ce5c00;font-weight:bold">&gt;</span><span style="color:#000">client_tracking_redirection</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#000;font-weight:bold">{</span>
    <span style="color:#8f5902;font-style:italic">// 2 找到转发的客户端实例
</span><span style="color:#8f5902;font-style:italic"></span>    <span style="color:#000">client</span> <span style="color:#ce5c00;font-weight:bold">*</span><span style="color:#000">redir</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">lookupClientByID</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">c</span><span style="color:#ce5c00;font-weight:bold">-</span><span style="color:#ce5c00;font-weight:bold">&gt;</span><span style="color:#000">client_tracking_redirection</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">;</span>
    <span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000;font-weight:bold">(</span><span style="color:#ce5c00;font-weight:bold">!</span><span style="color:#000">redir</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#000;font-weight:bold">{</span>
        <span style="color:#8f5902;font-style:italic">// 3 如果转发客户端关闭了，则必须通知原客户端
</span><span style="color:#8f5902;font-style:italic"></span>        <span style="color:#000;font-weight:bold">.</span><span style="color:#000;font-weight:bold">.</span><span style="color:#000;font-weight:bold">.</span><span style="color:#000;font-weight:bold">.</span>
        <span style="color:#204a87;font-weight:bold">return</span><span style="color:#000;font-weight:bold">;</span>
    <span style="color:#000;font-weight:bold">}</span>
    <span style="color:#000">c</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">redir</span><span style="color:#000;font-weight:bold">;</span>
    <span style="color:#000">using_redirection</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">1</span><span style="color:#000;font-weight:bold">;</span>
<span style="color:#000;font-weight:bold">}</span>

<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000;font-weight:bold">(</span><span style="color:#000">c</span><span style="color:#ce5c00;font-weight:bold">-</span><span style="color:#ce5c00;font-weight:bold">&gt;</span><span style="color:#000">resp</span> <span style="color:#ce5c00;font-weight:bold">&gt;</span> <span style="color:#0000cf;font-weight:bold">2</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#000;font-weight:bold">{</span>
    <span style="color:#8f5902;font-style:italic">// 4 如果是 RESP3 则发PUSH
</span><span style="color:#8f5902;font-style:italic"></span>    <span style="color:#000">addReplyPushLen</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">c</span><span style="color:#000;font-weight:bold">,</span><span style="color:#0000cf;font-weight:bold">2</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">;</span>
    <span style="color:#000">addReplyBulkCBuffer</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">c</span><span style="color:#000;font-weight:bold">,</span><span style="color:#4e9a06"></span><span style="color:#4e9a06">&#34;</span><span style="color:#4e9a06">invalidate</span><span style="color:#4e9a06">&#34;</span><span style="color:#000;font-weight:bold">,</span><span style="color:#0000cf;font-weight:bold">10</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">;</span>
<span style="color:#000;font-weight:bold">}</span> <span style="color:#204a87;font-weight:bold">else</span> <span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000;font-weight:bold">(</span><span style="color:#000">using_redirection</span> <span style="color:#ce5c00;font-weight:bold">&amp;</span><span style="color:#ce5c00;font-weight:bold">&amp;</span> <span style="color:#000">c</span><span style="color:#ce5c00;font-weight:bold">-</span><span style="color:#ce5c00;font-weight:bold">&gt;</span><span style="color:#000">flags</span> <span style="color:#ce5c00;font-weight:bold">&amp;</span> <span style="color:#000">CLIENT_PUBSUB</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#000;font-weight:bold">{</span>
    <span style="color:#8f5902;font-style:italic">// 5 转发模式，往 TrackingChannelName 信道中发送消息
</span><span style="color:#8f5902;font-style:italic"></span>    <span style="color:#000">addReplyPubsubMessage</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">c</span><span style="color:#000;font-weight:bold">,</span><span style="color:#000">TrackingChannelName</span><span style="color:#000;font-weight:bold">,</span><span style="color:#204a87">NULL</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">;</span>
<span style="color:#000;font-weight:bold">}</span> <span style="color:#204a87;font-weight:bold">else</span> <span style="color:#000;font-weight:bold">{</span>
    <span style="color:#204a87;font-weight:bold">return</span><span style="color:#000;font-weight:bold">;</span>
<span style="color:#000;font-weight:bold">}</span>
<span style="color:#8f5902;font-style:italic">// 6 发送键等信息，和上边4，5操作连在一起的。
</span><span style="color:#8f5902;font-style:italic"></span><span style="color:#000">addReplyProto</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">c</span><span style="color:#000;font-weight:bold">,</span><span style="color:#000">keyname</span><span style="color:#000;font-weight:bold">,</span><span style="color:#000">keylen</span><span style="color:#000;font-weight:bold">)</span><span style="color:#000;font-weight:bold">;</span>
<span style="color:#000;font-weight:bold">}</span></code></pre></div></li>
</ul>

<h3 id="后记">后记</h3>

<p>相信看到这里的小伙伴们都已经有点疲惫了吧，但是还请大家多多点赞多多评论。后续还会学习其他 Redis 6.0.0 的其他亮点功能，请大家继续关注。</p>

<h3 id="参考">参考</h3>

<ul>
<li><a href="https://redis.io/topics/client-side-caching">https://redis.io/topics/client-side-caching</a></li>
<li><a href="https://tech.meituan.com/2017/03/17/cache-about.html">https://tech.meituan.com/2017/03/17/cache-about.html</a></li>
<li><a href="https://cloud.tencent.com/developer/article/1005399">https://cloud.tencent.com/developer/article/1005399</a></li>
<li><a href="https://juejin.im/post/5b849878e51d4538c77a974a">https://juejin.im/post/5b849878e51d4538c77a974a</a></li>
<li><a href="https://www.kawabangga.com/posts/3590">https://www.kawabangga.com/posts/3590</a></li>
<li>ppt <a href="https://www.slideshare.net/RedisLabs/redisconf18-techniques-for-synchronizing-inmemory-caches-with-redis">https://www.slideshare.net/RedisLabs/redisconf18-techniques-for-synchronizing-inmemory-caches-with-redis</a></li>
<li>视频 <a href="https://www.bilibili.com/video/BV1qe411p7v9/">https://www.bilibili.com/video/BV1qe411p7v9/</a></li>
</ul>

    </div>
    <footer class="post-footer">
     

     <div class="post-nav">
    <div class="post-nav-next post-nav-item">
    
        <a href="http://www.shutdown.cn/post/redis6.0%E6%96%B0%E7%89%B9%E6%80%A7-%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%BC%93%E5%AD%98%E5%85%B6%E4%BA%94-java%E4%BD%BF%E7%94%A8%E5%AE%9E%E8%B7%B5/" rel="next" title="">
        <i class="fa fa-chevron-left"></i> 
        </a>
    
    </div>

    <div class="post-nav-prev post-nav-item">
    
        <a href="http://www.shutdown.cn/post/redis6.0%E6%96%B0%E7%89%B9%E6%80%A7-%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%BC%93%E5%AD%98%E5%85%B6%E5%9B%9B-%E4%B8%89%E7%A7%8D%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%BC%93%E5%AD%98%E7%9A%84%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%BC%8F/" rel="prev" title="">
         <i class="fa fa-chevron-right"></i>
        </a>
    
    </div>
</div>
      
     
     
     






    </footer>
  </article>
</section>

          </div>
        </div>
        <div class="sidebar-toggle">
  <div class="sidebar-toggle-line-wrap">
    <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
    <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
    <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
  </div>
</div>
<aside id="sidebar" class="sidebar">
  <div class="sidebar-inner">

    <section class="site-overview sidebar-panel  sidebar-panel-active ">
      <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image"
        src="http://www.shutdown.cn/img/author.jpg"
        alt="不与天斗Domino" />
    <p class="site-author-name" itemprop="name">不与天斗Domino</p>
    <p class="site-description motion-element" itemprop="description"> 
        Programmer &amp; Architect</p>
</div>
      <nav class="site-state motion-element">
    <div class="site-state-item site-state-posts">
      <a href="http://www.shutdown.cn/post/">
        <span class="site-state-item-count">183</span>
        <span class="site-state-item-name">日志</span>
      </a>
    </div>
    <div class="site-state-item site-state-categories">    
        <a href="http://www.shutdown.cn/categories/">      
         
        <span class="site-state-item-count">15</span>
        
        <span class="site-state-item-name">分类</span>
        
        </a>
    </div>

    <div class="site-state-item site-state-tags">
        <a href="http://www.shutdown.cn/tags/">
         
        <span class="site-state-item-count">224</span>
        
        <span class="site-state-item-name">标签</span>
        </a>
    </div>
</nav>
      
      

      

      <div class="links-of-blogroll motion-element inline">
<script type="text/javascript" src="//rf.revolvermaps.com/0/0/8.js?i=&amp;m=0&amp;s=220&amp;c=ff0000&amp;cr1=ffffff&amp;f=arial&amp;l=33&amp;bv=35" async="async"></script>
</div>

    </section>
    
  </div>
</aside>

      </div>
    </main>
   
    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright" >
  <span itemprop="copyrightYear">  &copy; 
  2013 - 2023</span>
  <span class="with-love"><i class="fa fa-heart"></i></span>
  <span class="author" itemprop="copyrightHolder">天地维杰网</span>
  <span class="icp" itemprop="copyrightHolder"><a href="https://beian.miit.gov.cn/" target="_blank">京ICP备13019191号-1</a></span>
</div>
<div class="powered-by">
  Powered by - <a class="theme-link" href="http://gohugo.io" target="_blank" title="hugo" >Hugo v0.63.2</a>
</div>
<div class="theme-info">
  Theme by - <a class="theme-link" href="https://github.com/xtfly/hugo-theme-next" target="_blank"> NexT
  </a>
</div>


      </div>
    </footer>

    <div class="back-to-top">
      <i class="fa fa-arrow-up"></i>
      <span id="scrollpercent"><span>0</span>%</span>
    </div>
  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>
<script type="text/javascript" src="http://www.shutdown.cn/js/vendor/jquery/index.js?v=2.1.3"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/vendor/fastclick/lib/fastclick.min.js?v=1.0.6"></script> 
<script type="text/javascript" src="http://www.shutdown.cn/js/vendor/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/vendor/velocity/velocity.min.js?v=1.2.1"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/vendor/velocity/velocity.ui.min.js?v=1.2.1"></script>
<script src="http://www.shutdown.cn/js/vendor/ua-parser-js/dist/ua-parser.min.js?v=0.7.9"></script>

<script src="http://www.shutdown.cn/js/vendor/fancybox/jquery.fancybox.pack.js?v=2.1.5"></script>

<script type="text/javascript" src="http://www.shutdown.cn/js/utils.js"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/motion.js"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/affix.js"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/schemes/pisces.js"></script>

<script type="text/javascript" src="http://www.shutdown.cn/js/scrollspy.js"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/post-details.js"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/toc.js"></script>

<script type="text/javascript" src="http://www.shutdown.cn/js/bootstrap.js"></script>

<script type="text/javascript" src="http://www.shutdown.cn/js/search.js"></script>
<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
    extensions: ["tex2jax.js"],
    jax: ["input/TeX", "output/HTML-CSS"],
    tex2jax: {
      inlineMath: [ ['$','$'] ],
      displayMath: [ ['$$','$$'] ],
      processEscapes: true
    },
    "HTML-CSS": { fonts: ["TeX"] }
  });
</script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML' async></script>
</body>
</html>